package com.jxpanda.commons.toolkit;


import com.jxpanda.commons.base.Coordinate;

/**
 * 地图类工具
 * 主要有一个计算两个坐标间距离的算法
 */
public class AtlasKit {

    /**
     * 地球半径，单位：米
     * 6371.393千米
     */
    public static final double EARTH_RADIUS = 6371393.0;

    /**
     * 180°
     */
    public static final double ANGLE_180 = 180.0;

    /**
     * 火星坐标（GCJ02）转 百度BD09坐标系的偏移值
     */
    public static final double GCJ02_TO_BD09_OFFSET = (Math.PI * 3000.0) / ANGLE_180;

    /**
     * 计算两个坐标之间的距离，计算结果的单位是【米】
     * 维基百科推荐使用Haversine公式，理由是Great-circle distance公式用到了大量余弦函数，
     * 而两点间距离很短时（比如地球表面上相距几百米的两点），余弦函数会得出0.999...的结果，
     * 会导致较大的舍入误差。而Haversine公式采用了正弦函数，即使距离很小，也能保持足够的有效数字。
     * 公式太复杂了，看代码吧，维基百科上抄来的
     */
    public static double distance(Coordinate coordinateA, Coordinate coordinateB) {
        double longitudeA = coordinateA.getRadianValue().getLongitude();
        double latitudeA = coordinateA.getRadianValue().getLatitude();
        double longitudeB = coordinateB.getRadianValue().getLongitude();
        double latitudeB = coordinateB.getRadianValue().getLatitude();

        double alpha = latitudeA - latitudeB;
        double theta = longitudeA - longitudeB;

        double delta = haversine(alpha) + Math.cos(latitudeA) * Math.cos(latitudeB) * haversine(theta);

        return EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(delta), Math.sqrt(1 - delta));

    }

    /**
     * 腾讯/高德等使用GCJ02的坐标系转为百度用的BD09坐标系
     *
     * @param coordinate GCJ02坐标系坐标
     * @return BD09坐标系坐标
     */
    public static Coordinate gcj02ToBd09(Coordinate coordinate) {
        double longitude = coordinate.getLongitude();
        double latitude = coordinate.getLatitude();

        // 公式是网上抄来的，不要问为什么要这么写，我也不知道
        var z = Math.sqrt(longitude * longitude + latitude * latitude) + 0.00002 * Math.sin(latitude * GCJ02_TO_BD09_OFFSET);
        var theta = Math.atan2(latitude, longitude) + 0.000003 * Math.cos(longitude * GCJ02_TO_BD09_OFFSET);

        double newLongitude = z * Math.cos(theta) + 0.0065;
        double newLatitude = z * Math.sin(theta) + 0.006;
        return new Coordinate(newLongitude, newLatitude);
    }

    /**
     * Haversine公式（半正矢公式）
     * haversine(θ) = sin^2(θ/2) = (1-cos(θ)) / 2
     * 入参theta应该是弧度，而不是角度
     *
     * @param theta 弧度值
     */
    private static double haversine(double theta) {
        return Math.pow(Math.sin(theta / 2), 2);
    }

}
